/*
DES/3DES `CBC mode` and `PKCS5Padding` encryption and decryption tool.
*/
package xdes

import (
	"bytes"
	"crypto/cipher"
	"crypto/des"
)

//FixedKeySize是为了修正DES/3DES的key的长度.
//如果key长度超出要求则截取掉;短于要求则填充;
//limit是key限制的长度，DES是8，3DES是24;
//padding是填充的字节.
func FixedKeySize(key []byte, limit int, padding byte) []byte {
	var pad = func(c byte, n int) []byte {
		v := make([]byte, 0)
		for i := 0; i < n; i++ {
			v = append(v, c)
		}
		return v
	}
	length := len(key)
	if length != limit {
		skey := make([]byte, 0, limit)
		if length < limit {
			skey = append(skey, key...)
			skey = append(skey, pad(padding, (limit-length))...)
		} else {
			skey = append(skey, key[:limit]...)
		}
		return skey
	}
	return key
}

//len(key) = 8
func DesEncrypt(plainData, key []byte) (securityData []byte, err error) {
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	//直接使用key作为初始化向量(Not Better)
	blockMode := cipher.NewCBCEncrypter(block, key)

	plainData = pKCS5Padding(plainData, block.BlockSize())
	securityData = make([]byte, len(plainData))
	blockMode.CryptBlocks(securityData, plainData)

	return securityData, nil
}

//len(key) = 8
func DesDecrypt(securityData, key []byte) (plainData []byte, err error) {
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	blockMode := cipher.NewCBCDecrypter(block, key)

	plainData = make([]byte, len(securityData))
	blockMode.CryptBlocks(plainData, securityData)
	plainData = pKCS5UnPadding(plainData)

	return plainData, nil
}

//len(key) = 24
func TripleDesEncrypt(plainData, key []byte) (securityData []byte, err error) {
	block, err := des.NewTripleDESCipher(key)
	if err != nil {
		return nil, err
	}
	blockMode := cipher.NewCBCEncrypter(block, key[:block.BlockSize()])

	plainData = pKCS5Padding(plainData, block.BlockSize())
	securityData = make([]byte, len(plainData))
	blockMode.CryptBlocks(securityData, plainData)

	return securityData, nil
}

//len(key) = 24
func TripleDesDecrypt(securityData, key []byte) (plainData []byte, err error) {
	block, err := des.NewTripleDESCipher(key)
	if err != nil {
		return nil, err
	}
	blockMode := cipher.NewCBCDecrypter(block, key[:block.BlockSize()])

	plainData = make([]byte, len(securityData))
	blockMode.CryptBlocks(plainData, securityData)

	plainData = pKCS5UnPadding(plainData)

	return plainData, nil
}

func pKCS5Padding(text []byte, blockSize int) []byte {
	padding := blockSize - len(text)%blockSize
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(text, padText...)
}

func pKCS5UnPadding(text []byte) []byte {
	length := len(text)
	unpadding := int(text[length-1])
	return text[:(length - unpadding)]
}
